class Mvvm{
    constructor(options){
        console.log(options)
        this.$el = options.el;
        this.$data = options.data;
        if(this.$el){
            //数据劫持
            new Observer(this.$data);
            //模板编译,初始化视图
            new Compile(this.$el,this);
        }
    }
}

//被观察者
class Dep{
    constructor(){
        this.watchers = []; //存放所有的观察者
    }

    //添加观察者
    add(watcher){
        this.watchers.push(watcher);
    }

    //通知
    notify(){
        this.watchers.forEach(watcher => {
            watcher.update();
        })
    }
}

//观察者
class Watcher{
    constructor(vm,expr,cb){
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        //存储一个旧值
        this.oldVal = this.getVal()
    }

    getVal(){
        Dep.target = this;
        let value = compileUtil.getVal(this.expr,this.vm);
        Dep.target = null;
        return value
    }

    update(){
        let newVal = compileUtil.getVal(this.expr,this.vm);
        console.log(newVal,this.oldVal);
        if(newVal != this.oldVal){
            //如果新值和旧值不一致，更新视图
            console.log("=======如果新值和旧值不一致，更新视图======")
            this.cb(newVal); 
        }
    }
}

//vm.$watch(vm,'user.username',() => {})

//数据劫持
class Observer{
    constructor(data){
        this.data = data;
        this.observer(this.data);
    }

    observer(data){
        if(data && typeof data === 'object'){
            for(let i in data){
                this.observerData(data,i,data[i])
            }
        }
    }

    observerData(obj,key,value){
        console.log(value)
        let dep = new Dep();
        this.observer(value);
        Object.defineProperty(obj,key,{
            set(newVal){
                if(newVal != value){
                    value = newVal; //设置值，才会有新值
                    dep.notify(); //通知修改视图
                    console.log("设置值=====",newVal);
                }
            },
            get(){
                console.log("获取值");
                Dep.target && dep.add(Dep.target); //添加观察者
                //添加观察者
                return value
            }
        })
    }
}

//模板编译

class Compile{
    constructor(el,vm){
        this.vm = vm;
        //判断el是否是dom节点
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        
        let fragment = this.nodeToFragment(this.el);

        //模板编译，初始化视图
        this.compile(fragment);

        this.el.appendChild(fragment);
    }

    //判断指令是否为vue的指令
    isDirective(attr){
        return attr.startsWith('v-');
    }

    //编译元素节点
    compileElement(node){
        //获取元素节点的属性
        let attrs = node.attributes; //type v-model
        [...attrs].forEach(attr => {
            let {name,value:expr} = attr; 
            console.log(name,expr); 
            //判断属性是否是vue内的指令 v-model v-html v-text 
            if(this.isDirective(name)){
                //说明属性就是vue的指令
                console.log("=====",name); //v-model user.username vm.$data
                let [,directive] = name.split('-'); //[v,model]
                //更新视图
                compileUtil[directive](node,expr,this.vm)
            }
        })
    }

    //编译文本节点
    compileText(node){
        console.log("文本节点",node);
        //取到节点内容的值
        let content = node.textContent;  //回车字符  '{{user.username}}'
        if(/\{\{(.+?)\}\}/.test(content)){
            //更新视图
            //node content vm
            compileUtil.content(node,content,this.vm);
        }
    }

    //编译文本节点

    compile(node){
        console.log(node);
        let childNodes = node.childNodes; //只拿到了直属子节点
        // console.log("====",childNodes);

        [...childNodes].forEach(child => {
            //区分是文本节点还是元素节点
            if(this.isElementNode(child)){
                //是元素节点
                this.compileElement(child);
                this.compile(child);
            }else{
                //是文本节点
                this.compileText(child);
            }
        })
    }

    //把dom节点放到文档片段内
    nodeToFragment(node){
        //1.获取根节点下的所有子节点
        let childNodes = node.childNodes;
        console.log(childNodes);
        //2.创建文档片段 文档片段不在主dom树内
        let fragment = document.createDocumentFragment();
        //3.把所有的子节点都放在文档片段内
        [...childNodes].forEach(child => {
            fragment.appendChild(child);
        })
        return fragment
    }

    //判断当前节点是否是元素节点
    isElementNode(node){
        //元素节点
        return node.nodeType === 1
    }
}

let compileUtil = {
    //根据表达式去vm.$data里取值
    //user.username vm.$data {user:{username:'zs',age:18}}
    getVal(expr,vm){
        let arr = expr.split('.'); //[user,username];
        return arr.reduce((prev,cur) => {
            return prev[cur]
        },vm.$data)
    },

    setVal(vm,expr,val){
        //[user,username]
        expr.split('.').reduce((prev,cur,index,arr) => {
            if(index === (arr.length-1)){
                prev[cur] = val
            }
            return prev[cur]
        },vm.$data)
    },
    model(node,expr,vm){
        //input 初始化
        // node.value = 'zs'
        let val = this.getVal(expr,vm); //'zs'
        let fn = this.updater.model; //
        fn(node,val);
        //根据表达式从data内获取值，就需要添加观察者
        new Watcher(vm,expr,(newVal) => {
            console.log("-----------newVal",newVal);
            fn(node,newVal); //更新视图
        })
        /**
         * 
         * {
         *     user：{
         *          username：，
         *          age：
         *      }
         * }
        */
        node.addEventListener('input',(e) => {
            // user.username  e.target.value
            this.setVal(vm,expr,e.target.value);
        })
        
    },
    content(node,content,vm){
        //{{user.username}}
        let fn = this.updater.content;
        let value = content.replace(/\{\{(.+?)\}\}/g,(...argv) => {
            console.log(argv[1]);
            new Watcher(vm,argv[1],(newVal) => {
                fn(node,newVal); //更新视图
            })
            return this.getVal(argv[1],vm)
        })
       
        fn(node,value);
    },
    updater:{
        model(node,value){
            node.value = value;
        },
        content(node,value){
            node.textContent = value;
        }
    }
}